#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>

//.hpp -> 开源软件使用 声明和实现可以放在一个文件里

namespace my_blockqueue
{
    //默认为5
    const int default_cap = 5;

    template <class T>
    class BlockQueue
    {
    private:
        std::queue<T> bq_; //阻塞队列
        int cap_;          //队列的元素上限
        pthread_mutex_t mtx_; //保护临界资源的锁

        //1. 当生产满了的时候，就不要生产了(不要竞争锁了)，而应该让消费者来消费
        //2. 当消费空了，就不消费（不要竞争锁了）,应该让生产者来进行生产

        pthread_cond_t is_full_; //bq_满的， 消费者在该条件变量下等待人家唤醒就直接消费
        pthread_cond_t is_empty_; //bq_空的，生产者在该条件变量下等待

    private:
        //阻塞队列是否为满
        bool IsFull()
        {
            return bq_.size() == cap_;
        }

        bool IsEmpty()
        {
            return bq_.size() == 0;
        }
        
        //加锁
        void LockQueue()
        {
            pthread_mutex_lock(&mtx_);
        }

        //解锁
        void UnlockQueue()
        {
            pthread_mutex_unlock(&mtx_);
        }

        //生产者等待
        void ProducterWait()
        {
            //pthread_cond_wait
            //1. 调用的时候，会首先自动释放mtx_!,然后再挂起自己
            //2. 返回的时候，会首先自动竞争锁，获取到锁之后，才能返回！
            pthread_cond_wait(&is_empty_, &mtx_);
        }

        //消费者等待
        void ConsumerWait()
        {
            pthread_cond_wait(&is_full_, &mtx_);
        }

        //满了就唤醒消费者
        void WakeupComsumer()
        {
            pthread_cond_signal(&is_full_);
        }
        //空了唤醒生产者
        void WakeupProducter()
        {
            pthread_cond_signal(&is_empty_);
        }      
    public:

        BlockQueue(int cap = default_cap):cap_(cap)
        {
            pthread_mutex_init(&mtx_, nullptr);
            pthread_cond_init(&is_empty_, nullptr);
            pthread_cond_init(&is_full_, nullptr);
        }

        ~BlockQueue() 
        {
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&is_empty_);
            pthread_cond_destroy(&is_full_);
        }
    public:

        //const &:输入
        //*: 输出
        //&: 输入输出

        //生产
        void Push(const T &in)
        {
            //加锁
            LockQueue();
            //临界区
            if(IsFull()){ 
                //满了生产者等待
                //等待是把线程挂起，此时是拿着锁的
                ProducterWait();
            }

            //向队列中放数据，生产函数
            bq_.push(in);
            //超出2分之一的时候通知消费者来消费
           if(bq_.size() > cap_/2 )  WakeupComsumer();
            //解锁 
            UnlockQueue();
            //WakeupComsumer();

        }

        //消费
        void Pop(T *out)
        {
            LockQueue();
           
            if(IsEmpty()){ 
                //无法消费，消费者等待挂起等生产者生产
                ConsumerWait();
            }
             //从队列中拿数据，消费函数函数
            *out = bq_.front();
            bq_.pop();
            
            //剩余2分之一以下的时候通知生产者生产
           if(bq_.size() < cap_/2 ) WakeupProducter();
            UnlockQueue();
            
        }
    };
}